﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using DesignByContract;
using MadMediaManager.Core.Interfaces.DataAccess.Repositories;
using MadMediaManager.Core.Interfaces.Domain;
using NHibernate;
using NHibernate.Linq;

namespace MadMediaManager.DataAccess.Repositories
{
    public class NHibernateRepository<TEntity, TId> : ILinqEnabledRepository<TEntity, TId> where TEntity : IEntity<TId>, IDeletable
    {
        protected ISession Session { get; private set; }

        public NHibernateRepository (ISession session)
        {
            Check.Require(session != null, "session cannot be null");
            Session = session;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public virtual void Dispose()
        {
            Session = null;
        }

        /// <summary>
        /// Saves an new object to the store.
        /// </summary>
        /// <param name="entity">The object to save</param>
        public void Save(TEntity entity)
        {
            Session.Save(entity);
        }

        /// <summary>
        /// Retrieves an object from the data store.
        /// </summary>
        /// <param name="id">The unique identifier of the object.</param>
        /// <returns>The object if found.</returns>
        public TEntity Get(TId id)
        {
            return Session.Get<TEntity>(id);
        }

        /// <summary>
        /// Retrieves all <typeparamref name="TEntity"/> from the data store.
        /// </summary>
        /// <returns>A <see cref="IList{T}"/> of the objects.</returns>
        public IEnumerable<TEntity> GetAll()
        {
            return Query();
        }

        /// <summary>
        /// Updates an object in the data store.
        /// </summary>
        /// <param name="updated">The object to update containing the new values.</param>
        public void Update(TEntity updated)
        {
            Session.Update(updated);
        }

        /// <summary>
        /// Marks an object as deleted via <see cref="IDeletable"/>
        /// </summary>
        /// <param name="entity">The object to delete.</param>
        public void Delete(TEntity entity)
        {
            entity.IsDeleted = true;
        }

        /// <summary>
        /// Checks if the entity has a unique signature that does not conflict with other entities in the data store.
        /// </summary>
        /// <param name="entity">The entity to check</param>
        /// <returns>true if the entity is unique</returns>
        public bool IsUnique(TEntity entity)
        {
            var found = Get(entity.EntityId);
            if (found == null)
            {
                return true;
            }
            if (found.Equals(entity))
            {
                Session.Evict(found);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Get a single <typeparamref name="TEntity"/> based on an expression
        /// </summary>
        /// <param name="expression">The expression</param>
        /// <returns></returns>
        public TEntity Get(Expression<Func<TEntity, bool>> expression)
        {
            return Query().FirstOrDefault(expression);
        }

        /// <summary>
        /// Gets all records from the database meeting the requirements of the expression.
        /// </summary>
        /// <param name="expression">The expression used to evaluate.</param>
        /// <returns></returns>
        public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression)
        {
            return Query().Where(expression);
        }

        /// <summary>
        /// Returns a generic queryable link to the database with which Linq expressions can be used.
        /// Implementations of this method may be incomplete depending on the Linq provider.
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> Query()
        {
            return Session.Query<TEntity>();
        }
    }
}
